/* File: ast_stmt.cc
 * -----------------
 * Implementation of statement node classes.
 */
#include "ast_stmt.h"
#include "ast_type.h"
#include "ast_decl.h"
#include "ast_expr.h"
#include "errors.h"
#include "ast_type.h"
#include <typeinfo>

bool Scope::insertDecl(Decl *decl) {
  Decl *find = map->Lookup(decl->getName());
  if ( find != NULL ) {
    ReportError::DeclConflict(decl, find);
    return true;
  }
  map->Enter(decl->getName(),decl);
  return false;
}

Scope *Program::gScope = new Scope();

void Stmt::useScope(Scope *s) {
  scope->setParent(s);
}

Program::Program(List<Decl*> *d) {
    Assert(d != NULL);
    (decls=d)->SetParentAll(this);
}

void Program::Check() {
  useScope();
  for (int i = 0; i < decls->NumElements(); i++) {
    decls->Nth(i)->check();
  }
}

void Program::useScope() {
  for (int i = 0, n = decls->NumElements(); i < n; ++i) {
    gScope->insertDecl(decls->Nth(i));
  }
  for (int i = 0, n = decls->NumElements(); i < n; ++i) {
    decls->Nth(i)->useScope(gScope);
  }
}

StmtBlock::StmtBlock(List<VarDecl*> *d, List<Stmt*> *s) {
    Assert(d != NULL && s != NULL);
    (decls=d)->SetParentAll(this);
    (stmts=s)->SetParentAll(this);
}

void StmtBlock::check() {
	for( int i = 0; i < decls->NumElements(); i++ ) {
		decls->Nth(i)->check();
	}
	for( int i = 0; i < stmts->NumElements(); i++ ) {
		stmts->Nth(i)->check();
	}	  
}

void StmtBlock::useScope(Scope *s) {
  scope->setParent(s);
  for (int x = 0; x < decls->NumElements(); x++) {
    scope->insertDecl(decls->Nth(x));
  }
  for (int y = 0; y < decls->NumElements(); y++) {
    decls->Nth(y)->useScope(scope);
  }
  for (int z = 0; z < stmts->NumElements(); z++) {
    stmts->Nth(z)->useScope(scope);
  }
}

ConditionalStmt::ConditionalStmt(Expr *t, Stmt *b) { 
    Assert(t != NULL && b != NULL);
    (test=t)->SetParent(this); 
    (body=b)->SetParent(this);
}

void ConditionalStmt::check() {
  test->check();
  body->check();

  if (!test->getType()->equals(Type::boolType))
      ReportError::TestNotBoolean(test);

}

void ConditionalStmt::useScope(Scope *s) {
  scope->setParent(s);
  body->useScope(s);
  test->useScope(s);
}

ForStmt::ForStmt(Expr *i, Expr *t, Expr *s, Stmt *b): LoopStmt(t, b) { 
    Assert(i != NULL && t != NULL && s != NULL && b != NULL);
    (init=i)->SetParent(this);
    (step=s)->SetParent(this);
}

IfStmt::IfStmt(Expr *t, Stmt *tb, Stmt *eb): ConditionalStmt(t, tb) { 
    Assert(t != NULL && tb != NULL); // else can be NULL
    elseBody = eb;
    if (elseBody) elseBody->SetParent(this);
}

void IfStmt::useScope(Scope *s) {
  scope->setParent(s);
  test->useScope(scope);
  body->useScope(scope);
  if ( elseBody != NULL ) {
    elseBody->useScope(scope);
  }
}

void IfStmt::check() {
  test->check();
  body->check();

  if (! (test->getType()->equals(Type::boolType)) ) {
		//printf("%f is not a boolean", test);
    ReportError::TestNotBoolean(test);
  	
  }

  if (elseBody != NULL)
      elseBody->check();
}

ReturnStmt::ReturnStmt(yyltype loc, Expr *e) : Stmt(loc) {
    Assert(e != NULL);
    (expr=e)->SetParent(this);
}

void LoopStmt::useScope(Scope *s) {
  scope->setParent(s);
  scope->setLoopStmt(this);
  test->useScope(scope);
  body->useScope(scope);
}

void ReturnStmt::check() {	
  expr->check();

  FnDecl *d = NULL;
  Scope *s = scope;
  while (s != NULL) {
      if ((d = s->getFnDecl()) != NULL)
          break;

      s = s->getParent();	
  }
  //**CAREFUL
  if (d == NULL) {
      ReportError::Formatted(location, "error: return is outside a function");
      return;
  }
  Type *expected = d->getReturnType();
  Type *given = expr->getType();
  if (!given->equals(expected)) {
    ReportError::ReturnMismatch(this, given, expected);
  }	
}
  
void BreakStmt::check() {
  Scope *s = scope;
  while (s != NULL) {
      if (s->getLoopStmt() != NULL)
          return;

      s = s->getParent();
  }

  ReportError::BreakOutsideLoop(this);
}
  
void ReturnStmt::useScope(Scope *s) {
  scope->setParent(s);
  expr->useScope(s);
}
  
PrintStmt::PrintStmt(List<Expr*> *a) {    
    Assert(a != NULL);
    (args=a)->SetParentAll(this);
}

void PrintStmt::useScope(Scope *s) {
  scope->setParent(s);
  for(int i = 0;i<args->NumElements();i++){
    args->Nth(i)->useScope(scope);
  }
}

void PrintStmt::check() {
  for (int i = 0, n = args->NumElements(); i < n; ++i) {
      Type *given = args->Nth(i)->getType();

      if (!(given->equals(Type::intType) ||
            given->equals(Type::boolType) ||
            given->equals(Type::stringType)))
          ReportError::PrintArgMismatch(args->Nth(i), i+1, given);
  }

  for (int i = 0, n = args->NumElements(); i < n; ++i)
      args->Nth(i)->check();
}
